<!DOCTYPE html></script>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<style>
  body{
    background: rgb(255, 255, 255);
  }
  .g6-tooltip {
    border: 1px solid #e2e2e2;
    border-radius: 4px;
    font-size: 12px;
    color: #545454;
    background-color: rgba(255, 255, 255, 0.9);
    padding: 10px 8px;
    box-shadow: rgb(174, 174, 174) 0px 0px 10px;
  }
</style>
<body>
  <div id="tip">布局中，请稍候......</div>
  <div id="mountNode"></div>
<script src="../build/g6.js"></script>
<script src="./assets/d3-4.13.0.min.js"></script>

<script>
  const colors = [ '#f5222d', '#faad14', 
  '#a0d911', '#13c2c2', '#1890ff', '#b37feb', '#eb2f96' ];
  const beginColor = '#5b8c00'; // green
  const endColor = '#ff4d4f'; // red
  const testData = {
    nodes: [
      {
        id: '0',
        cluster: 1,
        exportValue: 10000,
        x: 100,
        y: 200,
        // label: 0
      },
      {
        id: '1',
        cluster: 1,
        exportValue: 10000,
        x: 200,
        y: 200,
        // label: 1
      },
      // {
      //   id: '2',
      //   region: '1'
      // },
      // {
      //   id: '3',
      //   region: '2'
      // }
    ],
    edges: [
      {
        source: '0',
        target: '1',
      },
      {
        source: '1',
        target: '0',
      },
      // {
      //   source: '2',
      //   target: '3',
      // },
      // {
      //   source: '3',
      //   target: '0',
      // },
    ]
  }
  d3.json("./assets/data/filtered-trade.json", function(data) {
    const nodes = data.nodes;
    const edges = data.edges;

    const nodeMap = new Map();
    const clusterMap = new Map();
    let cidx = 0;
    nodes.forEach(n => {
      nodeMap.set(n.id, n);
      let region = n.region.split(" ");
      if (n.region === 'East Asia') region = n.region; 
      else region = region[region.length - 1];

      if (clusterMap.get(region) === undefined) {
        clusterMap.set(region, cidx);
        cidx++;
      }
      const clusterId = clusterMap.get(region);
      const color = colors[clusterId % colors.length];
      n.style = {
        color,
        fill: color,
        stroke: '#666',
        lineWidth: 0.6
      };
      n.cluster = clusterId;
      n.importValue = 0;
      n.exportValue = 0;
    });
    // map the value of 
    edges.forEach(e => {
      if (e.value === '') e.value = 0;
      const v = parseFloat(e.value);
      nodeMap.get(e.source).exportValue += v;
      nodeMap.get(e.target).importValue += v;
    });
    mapValueToProp(nodes, 'exportValue', 'size', [2, 12]);
    const graph = new G6.Graph({
      container: 'mountNode',
      width: 800,
      height: 600,
      layout: {
        type: 'fruchterman',
        maxIteration: 8000,
        gravity: 10,
        clustering: true,
        clusterGravity: 30
      },
      fitView: true,
      linkCenter: true,
      defaultNode: {
        shape: 'circle',
        size: 5
      },
      defaultEdge: {
        shape: 'quadratic'
      },
      modes: {
        default: [ 'drag-node', 'drag-canvas', {
          type: 'tooltip',
          formatText(model) {
            let name = '';
            let countries = '';
            if (model.name) name = model.name + '<br/>';
            if (model.countries) countries = '<br/>Number of Countries: ' + model.countries;
            const text = name 
              + 'Export Value: ' + model.exportValue + "(1000USD)"
              + '<br/>Region: ' + model.region 
              + '<br/>Cluster: ' + model.cluster 
              + countries;
            return text;
          },
          shouldUpdate: e => {
            return true;
          }
        }]
    },
    });
    
    graph.on('afterlayout', () => {
      const tipDiv = document.getElementById('tip');
      tipDiv.innerHTML = '布局完成！';
    });

    graph.data(data);
    graph.render();

    const edgeItems = graph.getEdges();
    edgeItems.forEach(e => {
      const lineWidth = 0.4;
      const strokeOpacity = 0.2;
      let stroke =  'l(0) 0:' + beginColor + ' 1:' + endColor;
      const sourceModel = e.getSource().getModel();
      const targetModel = e.getTarget().getModel();
      if (sourceModel.x > targetModel.x) {
        stroke = 'l(0) 0:' + endColor + ' 1:' + beginColor;
      }
      e.update({
        style: {
          lineWidth,
          strokeOpacity,
          stroke
        }
      })
    });
    graph.paint();

    graph.on('node:click', e => { 
      const targetItem = e.item;
      const model = targetItem.getModel();
      const graphEdges = graph.getEdges();
      const graphNodes = graph.getNodes();
      // click on the cluster node
      if (model.id.substr(0, 7) === 'cluster') {
        graphNodes.forEach(gn => {
          const gnModel = gn.getModel();
          // show the common nodes
          if (gnModel.cluster === model.cluster && gnModel.id.substr(0, 7) != 'cluster') {
            gn.show();
          }
          // remove the cluster nodes
          if (gnModel.id === model.id) graph.removeItem(gn);
        });
        
        graphEdges.forEach(ge => {
          const sourceModel = ge.get('sourceNode').getModel();
          const targetModel = ge.get('targetNode').getModel();
          // show the common edges
          if ((sourceModel.cluster === model.cluster && sourceModel.id.substr(0, 7) !== 'cluster') 
          || (targetModel.cluster === model.cluster && targetModel.id.substr(0, 7) !== 'cluster')) {
            ge.show();
            // add the edges to other cluster nodes
            if (!ge.get('sourceNode').get('visible') && sourceModel.cluster !== model.cluster) {
              let c1 = beginColor, c2 = endColor;
              if (model.x > targetModel.x) {
                c1 = endColor;
                c2 = beginColor;
              }
              graph.addItem('edge', {
                source: 'cluster' + sourceModel.cluster,
                target: targetModel.id,
                id: 'cluster-edge-' + ge.id,
                style: {
                  stroke: 'l(0) 0:' + c1 + ' 1:' + c2,
                  lineWidth: 0.4,
                  strokeOpacity: 0.2
                }
              });
            } else if (ge.get('targetNode').get('visible') && targetModel.cluster !== model.cluster) {
              let c1 = beginColor, c2 = endColor;
              if (sourceModel.x > model.x) {
                c1 = endColor;
                c2 = beginColor;
              }
              graph.addItem('edge', {
                source: sourceModel.id,
                target: 'cluster' + targetModel.id,
                id: 'cluster-edge-' + ge.id,
                style: {
                  stroke: 'l(0) 0:' + c1 + ' 1:' + c2,
                  lineWidth: 0.4,
                  strokeOpacity: 0.2
                }
              });
            }
            // hide the edges to the common nodes in other clusters
            if (!ge.get('sourceNode').get('visible') || !ge.get('targetNode').get('visible')) {
              ge.hide();
            }
          }
          // remove the cluster edges
          if (sourceModel.id === model.id || targetModel.id === model.id) {
            graph.removeItem(ge);
          }
        });
      } else { // click on the common node, cllapse them
      // calculate the cluster center
      const center = {x: 0, y: 0, count: 0, exportValue: 0};
      nodes.forEach(n => {
        if (n.cluster === model.cluster) {
          center.x += n.x;
          center.y += n.y;
          center.count++;
          center.exportValue += n.exportValue;
        }
      });
      center.x /= center.count;
      center.y /= center.count;
      // add cluster node on the center
      const size = center.count * 1;
      const clusterNodeId = 'cluster' + model.cluster;
      const color = colors[ model.cluster % colors.length ];
      const regionStrs = model.region.split(' ');
      let region = regionStrs[regionStrs.length - 1];
      if (model.region === 'East Asia') region = model.region;
      let labelPosition = 'center';
      if (region.length > size) labelPosition = 'left';
      const clusterNode = graph.addItem('node', {
          x: center.x,
          y: center.y,
          id: clusterNodeId,
          cluster: model.cluster,
          region: region,
          countries: center.count,
          exportValue: center.exportValue,
          style: {
            color,
            fill: color,
            stroke: '#666',
            lineWidth: 0.6
          },
          size,
          label: region,
          labelCfg: {
            style: { fontSize: 8.5 },
            position: labelPosition
          }
      });

      // add edges about the cluster
      graphEdges.forEach(ge => {
        const sourceModel = ge.get('sourceNode').getModel();
        const targetModel = ge.get('targetNode').getModel();
        if (!ge.get('sourceNode').get('visible') || !ge.get('targetNode').get('visible')) return;
        if (sourceModel.cluster === model.cluster && targetModel.cluster !== model.cluster) {
          let c1 = beginColor, c2 = endColor;
          if (center.x > targetModel.x) {
            c1 = endColor;
            c2 = beginColor;
          }
          graph.addItem('edge', {
            source: clusterNodeId,
            target: targetModel.id,
            id: 'cluster-edge-' + ge.id,
            style: {
              stroke: 'l(0) 0:' + c1 + ' 1:' + c2,
              lineWidth: 0.4,
              strokeOpacity: 0.2
            }
          });
        } else if (targetModel.cluster === model.cluster && sourceModel.cluster !== model.cluster) {
          let c1 = beginColor, c2 = endColor;
          if (sourceModel.x > center.x) {
            c1 = endColor;
            c2 = beginColor;
          }
          graph.addItem('edge', {
            source: sourceModel.id,
            target: clusterNodeId,
            id: 'cluster-edge-' + ge.id,
            style: {
              stroke: 'l(0) 0:' + c1 + ' 1:' + c2,
              lineWidth: 0.4,
              strokeOpacity: 0.2
            }
          });
        }
      });

      // hide the common nodes in the cluster
      graphNodes.forEach(gn => {
        if (gn.getModel().cluster === model.cluster 
        && gn.getModel().id.substr(0, 7) !== 'cluster') gn.hide();
      });
      // hide the common edges about cluster
      graphEdges.forEach(ge => {
        if (!ge.get('sourceNode').get('visible') || !ge.get('targetNode').get('visible')) ge.hide();
      });
      }
    });
  });
  function mapValueToProp(items, valueName, propName, range){
    const valueRange = [9999999999, -9999999999];
    items.forEach(n => {
      if (n[valueName] > valueRange[1]) valueRange[1] = n[valueName];
      if (n[valueName] < valueRange[0]) valueRange[0] = n[valueName];
    });
    const valueLength = valueRange[1] - valueRange[0];
    const rLength = range[1] - range[0];
    const propNameStrs = propName.split('.');
    if (propNameStrs[0] === 'style' && propNameStrs.length > 1) {
      items.forEach(n => {
        if (n.style === undefined) n.style = {};
        n.style[propNameStrs[1]] = rLength * (n[valueName] - valueRange[0]) / valueLength + range[0];
      });
    } else {
      items.forEach(n => {
        n[propNameStrs[0]] = rLength * (n[valueName] - valueRange[0]) / valueLength + range[0];
      });
    }
  }
</script>
</body>
</html>
